<body></body>

<script type="module">
	import lis from './getSequence.js'
	// 渲染器
	const renderer = createRenderer({
		createElement: (tag) => document.createElement(tag),
		setElementText: (el, text) => (el.textContent = text),
		insert: (el, parent, anchor = null) => parent.insertBefore(el, anchor),
		createText: (text) => document.createTextNode(text),
		createComment: (text) => document.createComment(text),
		setText: (el, text) => (el.nodeValue = text),

		patchProps: (el, key, prevValue, nextValue) => {
			// 特殊处理
			function shouldSetAsProps(el, key, value) {
				if (key === 'form' && el.tagName === 'INPUT') return false
				return key in el
			}
			// 事件处理（带时间戳过滤）
			if (key.startsWith('on')) {
				const invokers = el._vei || (el._vei = {})
				const name = key.slice(2).toLowerCase()

				if (nextValue) {
					if (!invokers[key]) {
						invokers[key] = (e) => {
							if (e.timeStamp < invokers[key].attached) return
							;[].concat(invokers[key].value).forEach((fn) => fn(e))
						}
						invokers[key].value = nextValue
						invokers[key].attached = performance.now()
						el.addEventListener(name, invokers[key])
					} else {
						invokers[key].value = nextValue
					}
				} else if (invokers[key]) {
					el.removeEventListener(name, invokers[key])
				}
			} else if (key === 'class') {
				// el.className = ''
			} else if (shouldSetAsProps(el, key, nextValue)) {
				const type = typeof el[key] //  获取该 DOM 属性值 的类型
				if (type === 'boolean' && nextValue === '') {
					el[key] = true // 如果是dom使用标准是布尔值，并且当前nextValue是空，将值矫正为true
				} else {
					el[key] = nextValue
				}
			} else {
				el.setAttribute(key, nextValue)
			}
		}
	})

	function createRenderer(optios) {
		const { createElement, setElementText, insert, createText, createComment, setText, patchProps } = optios

		// 挂载
		function mountElement(vnode, container, anchor) {
			const el = (vnode.el = createElement(vnode.type))

			// 处理子节点
			if (typeof vnode.children === 'string') {
				setElementText(el, vnode.children)
			} else if (Array.isArray(vnode.children)) {
				vnode.children.forEach((child) => patch(null, child, el))
			}

			// 处理属性
			if (vnode.props) {
				for (const key in vnode.props) {
					patchProps(el, key, null, vnode.props[key])
				}
			}

			insert(el, container, anchor) // 插入容器
		}

		// 卸载
		function unmount(vnode) {
			if (vnode.type === Fragment) {
				vnode.children.forEach(unmount) // 递归卸载片段
			} else {
				const parent = vnode.el.parentNode
				if (parent) parent.removeChild(vnode.el)
			}
		}

		// 渲染函数
		function render(vnode, container) {
			if (vnode) {
				patch(container._vnode, vnode, container) // 新vnode存在，将其与旧vnode一起传递给patch函数打补丁
			} else {
				if (container._vnode) {
					unmount(container._vnode) // newVNode不存在, 且 oldVNode存在 执行卸载(unmount)操作
				}
			}
			container._vnode = vnode // 保存节点
		}

		// 子节点更新：对比 字符串 | 数组 | null
		function patchChildren(oldVNode, newVNode, container) {
			if (typeof newVNode.children === 'string') {
				if (Array.isArray(oldVNode.children)) {
					oldVNode.children.forEach((c) => unmount(c)) // 只有当旧子节点为一组的时候，才需要逐个卸载，其他情况下什么都不需要做
				}
				setElementText(container, newVNode.children) // 最后将新的文本节点内容设置给容器元素
			} else if (Array.isArray(newVNode.children)) {
				if (Array.isArray(oldVNode.children)) {
					patchKeyedChildren(newVNode, oldVNode, container)
				} else {
					setElementText(container, '')
					newVNode.children.forEach((c) => patch(null, c, container))
				}
			} else {
				if (Array.isArray(oldVNode.children)) {
					oldVNode.children.forEach((v) => unmount(v)) // 旧的子节点要么文本子节点，要么不存在，但无论哪种情况，我们都只需要将容器清空，
					// 然后加新的一组字节对数个挂载
				} else if (typeof oldVNode.children === 'string') {
					setElementText(container, '')
				}
			}
		}

		// diff 对比
		function patchKeyedChildren(newVNode, oldVNode, container) {
			const oldChildren = oldVNode.children
			const newChildren = newVNode.children

			// 快速Diff算法
			let j = 0
			let oldStartVNode = oldChildren[j]
			let newStartVNode = newChildren[j]
			// 处理相同的前置, j指向两个Children的开头
			while (oldStartVNode.key === newStartVNode.key) {
				patch(oldStartVNode, newStartVNode, container)
				j++
				oldStartVNode = oldChildren[j]
				newStartVNode = newChildren[j]
			}
			//处理相同的后置
			let oldEndIndex = oldChildren.length - 1
			let newEndIndex = newChildren.length - 1
			let oldEndVNode = oldChildren[oldEndIndex]
			let newEndVNode = newChildren[newEndIndex]
			while (oldEndVNode.key === newEndVNode.key) {
				patch(oldEndVNode, newEndVNode, container)
				oldEndIndex--
				newEndIndex--
				oldEndVNode = oldChildren[oldEndIndex]
				newEndVNode = newChildren[newEndIndex]
			}
			// 处理相同前置和后置之后判断
			if (j <= newEndIndex && j > oldEndIndex) {
				//有相同 newVnode需要挂载，而oldVnode已经全部处理完了
				const anchorIndx = newEndIndex + 1,
					anchor = anchorIndx < newChildren.length ? newChildren[anchorIndx].el : null //如果超出了newChildren.length 那就挂载在后面
				while (j <= newEndIndex) {
					patch(null, newChildren[j++], container, anchor)
				}
			} else if (j > newEndIndex && j <= oldEndIndex) {
				// oldVnode需要卸载，newVnode已经全部处理完了
				while (j <= oldEndIndex) unmount(oldChildren[j++])
			} else {
				const count = newEndIndex - j + 1 //新的一组子节点中剩余未处理节点的数量
				const source = new Array(count).fill(-1) //新的节点在旧的节点的位置

				const oldStart = j
				const newStart = j
				let moved = false //是否需要移动
				let pos = 0 //在旧的节点里面最大索引

				const keyIndex = {}
				for (let i = newStart; i <= newEndIndex; i++) {
					keyIndex[newChildren[i].key] = i
				}

				let patched = 0 //patched 变量，代表更新过的节点数量

				for (let i = oldStart; i <= oldEndIndex; i++) {
					const oldVNode = oldChildren[i]
					// 假如更新过的节点数量小于需要更新的节点数量，那么就需要执行更新
					if (patched <= count) {
						// 通过索引表快速找到新的一组节点 中 具有相同key值节点位置
						const k = keyIndex[oldVNode.key]

						if (typeof k !== 'undefined') {
							newVNode = newChildren[k]
							patched++ //每更新一个节点，都将 patched 变量 +1
							patch(oldVNode, newVNode, container)
							source[k - newStart] = i
							if (k < pos) {
								// 判断节点是否需要移动
								moved = true
							} else {
								pos = k
							}
						} else {
							unmount(oldVNode)
						}
					} else {
						// 如果更新过的节点数量大于需要更新的节点数量，卸载多余的节点
						unmount(oldVNode)
					}
				}

				//如果 moved 为真，则需要进行 DOM 移动操作
				if (moved) {
					const seq = lis(source) // 计算最长递增子序列
					let i = count - 1 //i指向新的一组子节点的最后一个元素
					let s = seq.length - 1 // s指向最长递增子序列的最后一个元素

					for (i; i >= 0; i--) {
						if (source[i] == -1) {
							// 新节点挂载
							const pos = i + newStart
							const newVNode = newChildren[pos] // 该节点的下一个节点的位置索引
							const nextPos = pos + 1
							const anchor = nextPos < newChildren.length ? newChildren[nextPos].el : null // 锚点
							patch(null, newVNode, container, anchor)
						} else if (i !== seq[i]) {
							// 说明该节点需要移动
							// 该节点在新的一组子节点的真实位置
							for (i; i >= 0; i--) {
								const pos = i + newStart
								const newVNode = newChildren[pos] // 该节点的下一个节点的位置索引
								const nextPos = pos + 1
								const anchor = nextPos < newChildren.length ? newChildren[nextPos].el : null // 锚点
								insert(newVNode.el, container, anchor) // 移动
							}
						} else {
							// i===seq[i]时，说明该位置的节点不需要移动，s指向下一个位置
							s--
						}
					}
				}
			}
		}
		// 元素差异对比
		function patchElement(oldVNode, newVNode) {
			const el = (newVNode.el = oldVNode.el)
			const oldProps = oldVNode.props || {}
			const newProps = newVNode.props || {}

			// 更新属性 || 新增属性
			for (const key in newProps) {
				if (newProps[key] !== oldProps[key]) {
					patchProps(el, key, oldProps[key], newProps[key])
				}
			}
			for (const key in oldProps) {
				if (!key in newProps) {
					patchProps(el, key, oldProps[key], null)
				}
			}

			patchChildren(oldVNode, newVNode, el) // 更新children
		}

		// 补丁函数
		// anchor 描点元素
		function patch(oldVNode, newVNode, container, anchor) {
			// 新旧两者类型不同
			if ((oldVNode && oldVNode.type !== newVNode.type) || !newVNode) {
				unmount(oldVNode)
				oldVNode = null
			}
			// 以下代码： oldVNodeType === newVNodeType
			const type = newVNode.type
			if (typeof type === 'string') {
				if (!oldVNode) mountElement(newVNode, container, anchor) //挂载原生html元素类型
				else patchElement(oldVNode, newVNode) //对比更新
			} else if (type === Text) {
				if (!oldVNode) {
					const el = (newVNode.el = createText(newVNode.children)) //文本类型
					insert(el, container)
				} else {
					const el = (newVNode.el = oldVNode.el)
					if (newVNode.children !== oldVNode.children) setText(el, newVNode.children) //替换
				}
			} else if (type === Comment) {
				if (!oldVNode) {
					const el = (newVNode.el = createComment(newVNode.children)) //注释类型
					insert(el, container)
				} else {
					const el = (newVNode.el = oldVNode.el)
					if (newVNode.children !== oldVNode.children) setText(el, newVNode.children)
				}
			} else if (type === Fragment) {
				if (!oldVNode) newVNode.children.forEach((v) => patch(null, v, container)) //多节点类型
				else patchChildren(oldVNode, newVNode, container)
			} else if (typeof type === 'object') {
				// 组件类型的Vnode
				if (!oldVNode) {
					mountComponent(newVNode, container, anchor) //挂载组件
				} else {
					patchComponent(oldVNode, newVNode, anchor) //更新组件
				}
			} else if (type === 'xxx') {
				// 其他类型的Vnode
			}
		}

		// 更新 子组件的 props
		function resolveProps(options, propsData) {
			// 设置props的数据
			const props = {}
			const attrs = {}
			for (const key in propsData) {
				// 如果组件有定义props设置成合法的props，否则视为存放attrs
				if (options && key in options) {
					props[key] = propsData[key]
				} else {
					attrs[key] = propsData[key]
				}
			}
			return [props, attrs]
		}

		// props 是否发生变化
		function hasPropsChanged(preProps, nextProps) {
			const nextKeys = Object.keys(nextProps)
			// 数量上变化，有可能是新增或删除
			if (nextKeys.length !== Object.keys(preProps).length) {
				return true
			}
			// 有不相等的 props ，说明有变化
			for (let i = 0; i < nextKeys.length; i++) {
				const key = nextKeys[i]
				if (nextProps[key !== preProps[key]]) {
					return true
				}
			}
			return false
		}
		// 组件更新
		function patchComponent(preProps, nextProps, anchor) {
			const instance = (newVNode.component = oldVNode.component)
			const { props } = instance
			//检测子组件声明的props是否发生变化
			if (hasPropsChanged(oldVNode.props, newVNode.props)) {
				// 调用resolveProps函数获取props数据
				const [nextProps] = resolveProps(oldVNode.props, newVNode.props)
				// 更新props
				Object.keys(props).forEach((k) => {
					props[k] = nextProps[k]
				})
				// 删除不存在props
				Object.keys(props).forEach((k) => {
					if (!Boolean(k in props)) {
						delete props[k]
					}
				})
			}
		}

		// 任务缓存队列，用一个set表示，可以自动去重
		const queue = new Set()
		let isFlushing = false
		const p = Promise.resolve()
		// 调度器的主要：用来缓存将一个任务添加到缓冲队列中，并且开始刷新队列
		function queueJob(job) {
			queue.add(job)
			if (!isFlushing) {
				isFlushing = true // 避免重复刷新
				p.then(() => {
					try {
						queue.forEach((job) => job()) // 执行任务队列中的任务
					} finally {
						isFlushing = false // 重置状态
						queue.clear() //对于set需要clear
						queue.length = 0
					}
				})
			}
		}

		// 挂载组件
		function mountComponent(vnode, container, anchor) {
			const componentOptions = vnode.type // 通过vnode获取组的选项对象，vnode.type对象MyComponent
			const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: PropsOption, setup } = componentOptions // 获取组建的渲染函数 render

			beforeCreate && beforeCreate()

			const state = data ? reactive(data()) : null //调用 data 函数得到原始数据，并调用 reateive 函数将其包装成响应式数据，从而render函数内部可以通过this访问组件自身状态数据

			const [props, attrs] = resolveProps(PropsOption, vnode.props) // 调用resolveProps解析数据

			// 自身props，父组件的props
			const instance = {
				state,
				props: showReactive(props),
				isMounted: false,
				subTree: null
			}

			const setupContext = { attrs } //现在暂时只用 attrs
			const setupResult = setup(showReadonly(instance.props), setupContext) //setupContext作为第二个参数，setup返回对象或函数(返回vnode)组件渲染函数
			let setupState = null // 存储由 setup 返回的数据
			if (typeof setupResult === 'function') {
				if (render) console.err(`setup 函数返回渲染函数, render选项将被忽略`) //报告冲突setup函数，不使用render
				render = setupResult
			} else {
				setupState = setupResult
			}

			// 创建渲染上下文对象，本质上是组件实例的代理
			const renderContext = new Proxy(instance, {
				get(target, key, r) {
					const { state, props } = target
					if (state && key in state) {
						return state[key]
					} else if (props && key in props) {
						return props[key]
					} else if (setupState && key in setupState) {
						const value = setupState[key]
						return value.__v_isRef ? value.value : value
					} else {
						console.error(`读取的 ${key} 不存在`)
					}
				},
				set(target, key, value, r) {
					const { state, props } = target
					if (state && key in state) {
						state[key] = value
					} else if (props && key in props) {
						props[key] = value
					} else if (setupState && key in setupState) {
						const __v_isRef = setupState[key].__v_isRef
						if (__v_isRef) {
							setupState[key].value = value
						} else {
							setupState[key] = value
						}
					} else {
						console.error(`修改的 ${key} 不存在`)
					}
				}
			})
			vnode.component = renderContext

			created && created.call(renderContext) // 生命周期换绑代理上渲染上下文对象 renderContext

			effect(
				() => {
					const subTree = render.call(renderContext) // 执行渲染函数，获取组件的渲染内容，render函数返回的虚拟dom

					// 如果还没有挂载
					if (!instance.isMounted) {
						beforeMount && beforeMount.call(renderContext)
						patch(null, subTree, container, anchor) // 最后调用 patch 函数来挂在组件所描述的内容，即subTree
						instance.isMounted = true
						mounted && mounted.call(renderContext)
					} else {
						// 已经挂载
						beforeUpdate && beforeUpdate.call(renderContext)
						patch(instance.subTree, subTree, container, anchor)
						updated && updated.call(renderContext)
					}

					instance.subTree = subTree
				},
				{ scheduler: queueJob } //指定副作用函数的调度器为queueJob
			)
		}
		return { render }
	}
	const Fragment = Symbol()
	const Text = Symbol()

	import { reactive, showReactive, effect, showReadonly, ref } from './my-vue.js'
	// 组件本身是一个 对象，对象的 render函数 返回一个 虚拟DOM
	const MyComponent = {
		name: 'MyComponent',
		props: { title: String },
		setup(props, attrs) {
			const count = ref(0)
			// console.log('props', props, 'attrs', attrs)
			console.log('count', count.value)

			return { count }
			// return () => {
			// 	return { type: 'div', children: 'hello' }//组件渲染函数
			// }
		},
		render() {
			// 用 this访问 setup 暴露到渲染函数中
			return {
				type: 'div',
				children: `count:${this.count}`
			}
		}
	}
	// 一个用来描述组件的VNode对象,type属性值为组件的选项对象
	const CompVNode = {
		type: MyComponent,
		props: {
			title: 'big',
			other: ' this.val'
		}
	}
	renderer.render(CompVNode, document.body) //调用渲染器渲染组件
</script>
